
#include "FileSystem.h"

using namespace std;

FileSystem::FileSystem(LowLevelDisk *ll){
	_LowLevelDisk = ll;
}

void FileSystem::IncHLinkNum(int inode)
{
	_LowLevelDisk->incHardlinksNumber(inode);
}

int FileSystem::getEntriesPerBlock()
{
	return _LowLevelDisk->getEntriesPerBlock();
}

int FileSystem::createFile(char flag)
// – creates a file returning its i-node number.
// Flag indicates whether the file to create is a regular file -0 or a soft link-2.
{
	int inode = _LowLevelDisk->allocateInode();
	//function that puts '0' in all inode details.
	//_LowLevelDisk->initInode(inode);		change
	_LowLevelDisk->setInodeType(inode, flag);

	return inode;
}

int FileSystem::createDir()
// – creates a directory, returning its i-node number.
{
	int inode = _LowLevelDisk->allocateInode();
	//_LowLevelDisk->initInode(inode);		change
	_LowLevelDisk->setInodeType(inode, 1);

	return inode;
}

int FileSystem::getFileType(int inode)
// – Returns the type of the file this i-node represents.
{
	return _LowLevelDisk->getInodeType(inode);
}


void FileSystem::f_read(int inode, char* buffer,int offset,int nBytes )
{
	// – reads nBytes (at most) from the given file
	//(represented by this i-node) at the given offset inside the file into the buffer.

	if(_LowLevelDisk->getInodeType(inode) == 1)
	{//if directory
		fwrite("cant read directory", 19, 1, stderr );
		return;
	}

	int fileSize = _LowLevelDisk->getFileSize(inode);
	int bytesToRead = min(fileSize-offset, nBytes);
	int byteLeftToRead = bytesToRead - ((_LowLevelDisk->getBlockSize()) - offset);

	int numOfBlocksToRead = 1;
	if(bytesToRead > ( (_LowLevelDisk->getBlockSize()) - offset))
	{
		numOfBlocksToRead = ceil((double)byteLeftToRead / (_LowLevelDisk->getBlockSize()));
	}
	int blockNumToRead = offset / (_LowLevelDisk->getBlockSize());
	int buffIndex = 0;
	for(int i = 0; i<numOfBlocksToRead; i++)
	{
		int tmpOffset = 0;
		char* tmpBuffer = (char*) malloc(_LowLevelDisk->getBlockSize());
		int db =_LowLevelDisk->getDataBlock(inode,blockNumToRead);
		_LowLevelDisk->readBlock(db,tmpBuffer);

		//if reading the first block:
		if(i==0)
		{
			tmpOffset = offset;
			for(int k = 0; k < ( (_LowLevelDisk->getBlockSize()) - offset); k++)
			{
				buffer[buffIndex] = tmpBuffer[tmpOffset];
				buffIndex++;
				tmpOffset++;
				byteLeftToRead--;
			}
		}
		else if( i+1 == numOfBlocksToRead )//if last block to read
		{
			tmpOffset = 0;
			for(int i = 0; i<byteLeftToRead; i++)
			{
				buffer[buffIndex] = tmpBuffer[tmpOffset];
				buffIndex++;
				tmpOffset++;
			}
		}
		else //regular inside block, need to copy all of it
		{
			tmpOffset = 0;
			for(int i = 0; i<_LowLevelDisk->getBlockSize(); i++)
			{
				buffer[buffIndex] = tmpBuffer[tmpOffset];
				buffIndex++;
				tmpOffset++;
			}
		}
		blockNumToRead++;
	}
}

void FileSystem::f_write(int inode,char* buffer,int offset,int nBytes )
// – writes nBytes (at most) to the given file (represented by this i-node)
//   at the given offset inside that file, from the buffer.
//	 If the amount to be written exceeds the current allocated block, a new block should be
//   allocated to accommodate the rest of the data.
//	 Notice that both f_read and f_write only work on normal files (not directories).
{
	if(_LowLevelDisk->getInodeType(inode) == 1)
	{//if directory
		fwrite("cant read a directory", 19, 1, stderr );
		return;
	}

	//TODO: check cases when we cant write all data to file.

	int bytesWrtitten = 0;
	int blocksToAllocate = 0;
	int blockSize = _LowLevelDisk->getBlockSize();
	int fileSize = _LowLevelDisk->getFileSize(inode);
	int freeSpaceInFile = blockSize*(1+(fileSize/blockSize)) - offset;

	if(freeSpaceInFile < nBytes)
	{//need to alocate at least one block;
		blocksToAllocate = ceil((double)(nBytes-freeSpaceInFile)/blockSize);
	}

	/*writing first few bytes to the current block */
	int inodeBlockNum = offset/blockSize;
	char* tmpBuff = (char*)malloc(blockSize);
	int dbNum = _LowLevelDisk->getDataBlock(inode, inodeBlockNum);
	_LowLevelDisk->readBlock(dbNum,tmpBuff);
	int blockOffset = offset - (offset/blockSize)*blockSize;
	int size = min(nBytes,freeSpaceInFile);
	int bufferIndex = 0;

	for(int i = 0; i < size; i++)
	{
		tmpBuff[blockOffset] = buffer[bufferIndex];
		bytesWrtitten++;
		bufferIndex++;
		blockOffset++;
	}

	_LowLevelDisk->writeBlock(dbNum,tmpBuff);

	while( blocksToAllocate > 0)
	{
		int bytesLeftToWrite = nBytes - bytesWrtitten;
		inodeBlockNum++;
		int newBlockNumber = _LowLevelDisk->allocateDataBlock(); // returns the block number that have been alocated.

		//update inode blocks list
		_LowLevelDisk->setDataBlock(inode, inodeBlockNum, newBlockNumber);

		//writing to block
		int tmpBufferIndex = 0;
		int howMuchToWrite = min (bytesLeftToWrite, blockSize);
		for(int i = 0; i<howMuchToWrite; i++)
		{   //copying data from origin buffer to tmp buffer.
			tmpBuff[tmpBufferIndex] = buffer[bufferIndex];
			bytesWrtitten++;
			bufferIndex++;
			tmpBufferIndex++;
		}

		//writing block to FILE_SYS
		_LowLevelDisk->writeBlock(newBlockNumber, tmpBuff);
		blocksToAllocate--;
	}
}

vector<LibEntry> FileSystem::d_read(int inode)
// – reads the entire content of the directory file specified by the given i-node.
//This information is then entered into a data structure (a vector or list) that lists the
//files in this directory. Each element in this list therefore contains a filename and i-node number.
{
	vector<LibEntry> filesList;

	if(_LowLevelDisk->getInodeType(inode) != 1)
	{//if not a directory
		cerr<<"not a directory- inode= "<<inode<<endl;
		return filesList;
	}

	int dirSize = _LowLevelDisk->getFileSize(inode);
	int numOfEntrysToRead = dirSize/getEntriesPerBlock();

	LibEntry entry;

	int offset = 0;
	int i = 0;
	while( i != numOfEntrysToRead )
	{
		myRead(inode,(char*)&entry,offset,getEntriesPerBlock());
		filesList.push_back(entry);
		offset+=getEntriesPerBlock();
		i++;
	}
	return filesList;
}


void FileSystem::d_write(int inode, vector<LibEntry> list )
{
	_LowLevelDisk->freeAtachedBlocks(inode);

	if(_LowLevelDisk->getInodeType(inode) != 1)
	{//if not directory
		cerr<<"d_write: file not dir : "<<inode<<endl;
		return;
	}

		//TODO: check cases when we cant write all data to file.

//		int bytesWrtitten = 0;
		int blocksToAllocate = 0;
		int blockSize = _LowLevelDisk->getBlockSize();
		int fileSize = list.size()*sizeof(vector<LibEntry>); //size* quantity
//		int freeSpaceInFile = blockSize*(1+(fileSize/blockSize)) ;


		/*writing first few bytes to the current block */
//		int inodeBlockNum = offset/blockSize;
		char* tmpBuff = (char*)malloc(blockSize);
//		int dbNum = _LowLevelDisk->getDataBlock(inode, inodeBlockNum);
//		_LowLevelDisk->readBlock(dbNum,tmpBuff);
//		int blockOffset = offset - (offset/blockSize)*blockSize;
//		int size = min(nBytes,freeSpaceInFile);
//		int bufferIndex = 0;

		for(int i = 0; i < size; i++)
		{
			tmpBuff[blockOffset] = buffer[bufferIndex];
			bytesWrtitten++;
			bufferIndex++;
			blockOffset++;
		}

		_LowLevelDisk->writeBlock(dbNum,tmpBuff);

		while( blocksToAllocate > 0)
		{
			int bytesLeftToWrite = nBytes - bytesWrtitten;
			inodeBlockNum++;
			int newBlockNumber = _LowLevelDisk->allocateDataBlock(); // returns the block number that have been alocated.

			//update inode blocks list
			_LowLevelDisk->setDataBlock(inode, inodeBlockNum, newBlockNumber);

			//writing to block
			int tmpBufferIndex = 0;
			int howMuchToWrite = min (bytesLeftToWrite, blockSize);
			for(int i = 0; i<howMuchToWrite; i++)
			{   //copying data from origin buffer to tmp buffer.
				tmpBuff[tmpBufferIndex] = buffer[bufferIndex];
				bytesWrtitten++;
				bufferIndex++;
				tmpBufferIndex++;
			}

			//writing block to FILE_SYS
			_LowLevelDisk->writeBlock(newBlockNumber, tmpBuff);
			blocksToAllocate--;
		}

}


void FileSystem::f_delete(int inode)
{
	// – delete the file.
	_LowLevelDisk->freeInode(inode);
}

void FileSystem::d_delete(int inode)
{
	// – delete the directory if it is empty.
	if(_LowLevelDisk->getFileSize(inode) == 0)
	{
		f_delete(inode);
	}
}

void FileSystem::myRead(int inode, char* buffer,int offset,int nBytes )
{
	// – reads nBytes (at most) from the given file
	//(represented by this i-node) at the given offset inside the file into the buffer.

	if(_LowLevelDisk->getInodeType(inode) == 1)
	{//if directory
		fwrite("cant read directory", 19, 1, stderr );
		return;
	}

	int fileSize = _LowLevelDisk->getFileSize(inode);
	int bytesToRead = min(fileSize-offset, nBytes);
	int byteLeftToRead = bytesToRead - ((_LowLevelDisk->getBlockSize()) - offset);

	int numOfBlocksToRead = 1;
	if(bytesToRead > ( (_LowLevelDisk->getBlockSize()) - offset))
	{
		numOfBlocksToRead = ceil((double)byteLeftToRead / (_LowLevelDisk->getBlockSize()));
	}
	int blockNumToRead = offset / (_LowLevelDisk->getBlockSize());
	int buffIndex = 0;
	for(int i = 0; i<numOfBlocksToRead; i++)
	{
		int tmpOffset = 0;
		char* tmpBuffer = (char*) malloc(_LowLevelDisk->getBlockSize());
		int db =_LowLevelDisk->getDataBlock(inode,blockNumToRead);
		_LowLevelDisk->readBlock(db,tmpBuffer);

		//if reading the first block:
		if(i==0)
		{
			tmpOffset = offset;
			for(int k = 0; k < ( (_LowLevelDisk->getBlockSize()) - offset); k++)
			{
				buffer[buffIndex] = tmpBuffer[tmpOffset];
				buffIndex++;
				tmpOffset++;
				byteLeftToRead--;
			}
		}
		else if( i+1 == numOfBlocksToRead )//if last block to read
		{
			tmpOffset = 0;
			for(int i = 0; i<byteLeftToRead; i++)
			{
				buffer[buffIndex] = tmpBuffer[tmpOffset];
				buffIndex++;
				tmpOffset++;
			}
		}
		else //regular inside block, need to copy all of it
		{
			tmpOffset = 0;
			for(int i = 0; i<_LowLevelDisk->getBlockSize(); i++)
			{
				buffer[buffIndex] = tmpBuffer[tmpOffset];
				buffIndex++;
				tmpOffset++;
			}
		}
		blockNumToRead++;
	}
}
